/*
 * Copyright (C) 2017 Samuel Wall
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.co.samuelwall.materialtaptargetprompt.extras.focals;

import ohos.agp.components.Component;
import ohos.agp.render.BlendMode;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptFocal;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptOptions;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptUtils;
import uk.co.samuelwall.materialtaptargetprompt.utils.Constants;
import uk.co.samuelwall.materialtaptargetprompt.utils.DensityUtils;

import org.jetbrains.annotations.Nullable;

/**
 * Prompt focal implementation to draw the focal as a rectangle.
 *
 * @since 2021-06-17
 */
public class RectanglePromptFocal extends PromptFocal {
    Paint mPaint;
    int mRippleAlpha;
    RectFloat mBounds;
    RectFloat mBaseBounds;
    Point mBaseBoundsCentre;
    RectFloat mRippleBounds;
    int mBaseAlpha;
    float mPadding;
    Path mPathOut;
    Path mPathIn;
    private float mRx;
    private float mRy;
    @Nullable
    private Point mSize;

    /**
     * Constructor.
     *
     * @param context context
     */
    public RectanglePromptFocal(Context context) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mBounds = new RectFloat();
        mBaseBounds = new RectFloat();
        mBaseBoundsCentre = new Point();
        mRippleBounds = new RectFloat();
        final float density = DensityUtils.getDeviceAttr(context).getAttributes().densityPixels;
        mRx = Constants.NUM_2 * density;
        mRy = Constants.NUM_2 * density;
        mPadding = Constants.NUM_8 * density;
    }

    /**
     * Set the radius for the rectangle corners.
     *
     * @param rx The x-radius of the oval used to round the corners
     * @param ry The y-radius of the oval used to round the corners
     * @return This prompt focal
     */
    public RectanglePromptFocal setCornerRadius(final float rx, final float ry) {
        mRx = rx;
        mRy = ry;
        return this;
    }

    /**
     * Set the padding between the target bounds and the rectangle edge.
     *
     * @param padding The distance from the target edge to the rectangle edge.
     * @return This prompt focal.
     */
    public RectanglePromptFocal setTargetPadding(final float padding) {
        mPadding = padding;
        return this;
    }

    /**
     * Sets the focal to a fixed size. Set as null to change it back to dynamic.
     *
     * @param size The fixed focal size or null to change back to the default dynamic size.
     * @return This prompt focal
     */
    public RectanglePromptFocal setSize(@Nullable final Point size) {
        if (size == null) {
            mSize = null;
        } else {
            mSize = new Point(size.getPointX(), size.getPointY());
        }
        return this;
    }

    @Override
    public RectFloat getBounds() {
        return mBaseBounds;
    }

    @Override
    public Path getPath() {
        return mPathOut;
    }

    @Override
    public void setColour(int colour) {
        mPaint.setColor(new Color(colour));
        mBaseAlpha = Color.alpha(colour);
        mPaint.setAlpha(mBaseAlpha);
    }

    @Override
    public void prepare(PromptOptions options, final int[] promptViewPosition) {
        if (options != null) {
            mPromptOptions = options;
            Component target = options.getTargetView();
            if (target != null) {
                final int[] targetPosition = target.getLocationOnScreen();
                final float left = targetPosition[0] - promptViewPosition[0];
                final float top = targetPosition[1] - promptViewPosition[1];
                final int width = target.getWidth();
                final int height = target.getHeight();
                if (mSize == null) {
                    mBaseBounds.left = left - mPadding;
                    mBaseBounds.top = top - mPadding;
                    mBaseBounds.right = left + width + mPadding;
                    mBaseBounds.bottom = top + height + mPadding;
                    mBaseBoundsCentre.modify(left + (width / Constants.NUM_2F), top + (height / Constants.NUM_2F));
                } else {
                    prepare(options, left + (width / Constants.NUM_2F), top + (height / Constants.NUM_2F));
                }
            }
        }
    }

    @Override
    public void prepare(PromptOptions options, float targetX, float targetY) {
        mPromptOptions = options;
        if (mSize != null) {
            final float halfWidth = mSize.getPointX() / 2;
            final float halfHeight = mSize.getPointY() / 2;
            mBaseBounds.left = targetX - halfWidth - mPadding;
            mBaseBounds.top = targetY - halfHeight - mPadding;
            mBaseBounds.right = targetX + halfWidth + mPadding;
            mBaseBounds.bottom = targetY + halfHeight + mPadding;
            mBaseBoundsCentre.modify(targetX, targetY);
        } else {
            throw new UnsupportedOperationException("RectanglePromptFocal size must be set using setSize(PointF)");
        }
    }

    @Override
    public void update(float revealModifier, float alphaModifier) {
        mPaint.setAlpha(alphaModifier * mBaseAlpha / Constants.NUM_255);
        Component target = mPromptOptions.getTargetView();
        PromptUtils.scale(mBaseBoundsCentre, mBaseBounds, mBounds, revealModifier, true);
        if (target != null) {
            if (mPromptOptions.getIconDrawable() == null) {
                mPathIn = new Path();
                mPathIn.addRect(mBaseBoundsCentre.getPointX() - target.getWidth() / Constants.NUM_2F,
                        mBaseBoundsCentre.getPointY() - target.getHeight() / Constants.NUM_2F,
                        mBaseBoundsCentre.getPointX() + target.getWidth() / Constants.NUM_2F,
                        mBaseBoundsCentre.getPointY() + target.getHeight() / Constants.NUM_2F,
                        Path.Direction.CLOCK_WISE);
            }
        }
        mPathOut = new Path();
        mPathOut.addRoundRect(mBounds, mRx, mRy, Path.Direction.CLOCK_WISE);
    }

    @Override
    public void updateRipple(float revealModifier, final float alphaModifier) {
        PromptUtils.scale(mBaseBoundsCentre, mBaseBounds, mRippleBounds, revealModifier, true);
        mRippleAlpha = (int) (mBaseRippleAlpha * alphaModifier);
    }

    @Override
    public void draw(Canvas canvas) {
        Component targetView = mPromptOptions.getTargetView();
        if (targetView != null && mPromptOptions.getIconDrawable() == null && mBaseBoundsCentre != null) {
            RectFloat rectFloat = new RectFloat(
                    0, 0,
                    mBaseBoundsCentre.getPointX() + targetView.getWidth() * Constants.NUM_2F,
                    mBaseBoundsCentre.getPointY() + targetView.getHeight() * Constants.NUM_2F);
            int layerId = canvas.saveLayer(rectFloat, new Paint());
            drawView(canvas);
            Paint paint = new Paint();
            paint.setColor(Color.TRANSPARENT);
            paint.setBlendMode(BlendMode.SRC);
            canvas.drawPath(mPathIn, paint);
            canvas.restoreToCount(layerId);
        } else {
            drawView(canvas);
        }
    }

    private void drawView(Canvas canvas) {
        final float oldAlpha = mPaint.getAlpha();
        final int oldColor = mPaint.getColor().getValue();
        Color color = new Color(oldColor);

        // Draw the ripple
        if (isDrawRipple) {
            if (oldColor == Color.TRANSPARENT.getValue()) {
                mPaint.setColor(Color.WHITE);
            }
            mPaint.setAlpha(1f * mRippleAlpha / Constants.NUM_255);
            canvas.drawRoundRect(mRippleBounds, mRx, mRy, mPaint);
        }
        mPaint.setColor(color);
        mPaint.setAlpha(oldAlpha);
        canvas.drawPath(getPath(), mPaint);
    }

    @Override
    public boolean contains(float x, float y) {
        return mBounds.isInclude(x, y);
    }
}
